package org.gwt.mosaic.xul.client;

import java.io.Serializable;


import java.util.ArrayList;
import java.util.List;

import org.gwt.mosaic.xul.client.ui.Container;
import org.gwt.mosaic.xul.client.ui.Element;
import org.gwt.mosaic.xul.client.ui.Observes;

import com.google.gwt.core.client.GWT;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;

/**
 * Client-side xml parser for {@link Element} objects. 
 * @author Alex Dobjanschi (alex.dobjanschi@gmail.com)
 * 
 * @see {@link Parser}
 *
 */
public class ElementParser {

	private static final String UI_PACKAGE_PREFIX = "org.gwt.mosaic.xul.client.ui.";

	private int level;
	private Element element;
	private ArrayList<Element> path = new ArrayList<Element>();

	/**
	 * Used to instantiate elements (derived from {@link Element})
	 */
	private static ElementInstantiator instantiator = GWT.create(ElementInstantiator.class);
	
	/**
	 * Used to set properties for different elements
	 */
	private static ElementAttributeSetter attributeSetter = GWT.create(ElementAttributeSetter.class);
	
	
	public Element parse (String content) {

		// create the document
		//
		Document document = XMLParser.parse(content);

		// process its contents
		//
		NodeList nodes = document.getChildNodes();
		
		for (int i = 0; i < nodes.getLength(); i++) {
			
			startNode(nodes.item(i));
		}
		
		//
		
		return element;
	}

	/**
	 * Coded, check results first
	 * @param name
	 */
	@SuppressWarnings("unchecked")
	private boolean startElement(String name) {
		String classname = UI_PACKAGE_PREFIX
					+ Character.toUpperCase(name.charAt(0)) + name.substring(1);
						
		GWT.log("startElement(" + classname + ")", null);
		// instantiation
		//
		Element c = instantiateElement (classname);
		if (c == null) {
			GWT.log("Unable to instantiate element class " + classname, null);
			return false;
		} else {

			if (level > 0) {
				if (c instanceof Observes) {
					List<Observes> list = (List<Observes>) element.getAttributeMap().get(
							Element.OBSERVES_ELEMENT_LIST);
					if (list == null) {
						list = new ArrayList<Observes>();
						element.getAttributeMap().put(Element.OBSERVES_ELEMENT_LIST,
								(Serializable) list);
					}
					list.add((Observes) c);
				} else if (element instanceof Container) {
					((Container) element).add(c);
				}
				element = c;
			} else if (level == 0) {
				element = c;
			}
			path.add(element);
			++level;
			
			return true;
		}
	}
	
	/** 
	 * @param className
	 * @return
	 */
	private Element instantiateElement (String className) {
		return instantiator.instantiate (className);
	}

	/**
	 * TODO Client-side code for XML parsing
	 * @param name
	 * @param value
	 */
	private void setAttribute (String name, String value) {
		GWT.log("setAttribute(target = " + element.getClass().getName() + ", name = " + name + ", value = " + value, null);
		attributeSetter.setAttribute(element, name, value);
	}
	

	/* TODO not yet coded
	private void characters(String text) {
		if (text.trim().length() > 0) {
			if (element instanceof Container) {
				Label label = new Label();
				label.setValue(text);
				((Container) element).add(label);
			} else if (element instanceof Description) {
				Description description = (Description) element;
				description.setValue(text, true);
			} else {
				setAttribute("label", text);
			}
		}
	}
	*/


	/**
	 * Client-side code for <code>DefaultParser.startElement</code> found in SAX parsers.
	 * @param nodeName
	 * 		The name of the node
	 * @param attributes
	 * 		Attributes list
	 */
	public void startNode (Node node) {
		if (startElement (node.getNodeName())) {

			if (node.getAttributes() != null) {
				for (int i = 0; i < node.getAttributes().getLength(); i++) {
					Node attribute = node.getAttributes().item(i);
					setAttribute(attribute.getNodeName(), attribute.getNodeValue());
				}
			}

			// inline traversal of the tree
			//
			NodeList children = node.getChildNodes();
			GWT.log("# children found = " + children.getLength(), null);
			if (children != null) {
				for (int i = 0; i < children.getLength(); i++) {
					//
					//
					startNode(children.item(i));
				}
			}

			// now go back
			// 
			endNode (node);

		}
	}
	
	/**
	 * Client-side code for <code>DefaultParser.endElement</code> found in SAX parsers
	 * @param node
	 * 		The node that ended
	 */
	public void endNode (Node node) {
		int n = path.size();
		if (n == 1) {
			path = null;
		} else {
			path.remove(n - 1);
			element = path.get(n - 2);
		}
		--level;		
	}	
}
